﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Upreader.Application;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Upreader.Application.Model;

namespace Upreader.Engine
{
    /// <summary>
    /// Helper class for loading segments when downloading
    /// </summary>
    class DownloadSegmentContextLoader
    {
        #region Fields

        // operation fields
        private readonly DownloadEngineContext _context;
        private readonly List<DownloadSegmentContext> _currentPendingProcessors;

        // synchronisation fields
        private ManualResetEventSlim _segmentsAvailableEvent;

        #endregion // Fields

        #region Constructor

        public DownloadSegmentContextLoader(DownloadEngineContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            _context = context;

            _currentPendingProcessors = new List<DownloadSegmentContext>();
            _context.Service.SegmentsChanged += new ModelsChangedEventHandler(OnServiceSegmentsChanged);

            // create a synchronization source and begin as signalled so to trigger a first run
            _segmentsAvailableEvent = new ManualResetEventSlim(true);
        }

        #endregion // Constructor

        #region Methods

        public Task Run(CancellationToken cancellationToken)
        {
            return Task.Factory.StartNew(() =>
            {
                try
                {
                    // Keep on running untill cancellation is requested
                    while (!cancellationToken.IsCancellationRequested)
                    {
                        // wait for the segmentAvailableEvent each time in order to make sure were not looping like crazy. 
                        // This event will be set initially and each time adds or updates are reported through the service
                        _segmentsAvailableEvent.Wait(cancellationToken);

                        // Wait might have released due to a requested cancellation, therefore check here
                        cancellationToken.ThrowIfCancellationRequested();

                        // In case new segments become available, we reset before loading. 
                        // Therefore we will not end up in a forever sleep
                        _segmentsAvailableEvent.Reset();

                        // remove all cached processors
                        _currentPendingProcessors.RemoveAll(x => x.State == DownloadSegmentContextState.Cached);

                        // Get the next buffer
                        IEnumerable<Segment> currentBuffer = _context.Service.GetSegmentsForDownload(
                            _context.Configuration.DownloadedSegmentProcessorCollectionBufferSize,
                            _currentPendingProcessors.Select(x => x.Segment.Id));

                        if (currentBuffer.Any())
                        {
                            // There are new segments available 
                            // therefore reset the event so we will check again after this round
                            _segmentsAvailableEvent.Set();

                            foreach (Segment segment in currentBuffer)
                            {
                                // make sure that when cancelled, we exit
                                cancellationToken.ThrowIfCancellationRequested();

                                DownloadSegmentContext segmentProcessor = new DownloadSegmentContext(segment);

                                _context.UnhandeledSegmentContextCollection.Add(segmentProcessor, cancellationToken);
                                _currentPendingProcessors.Add(segmentProcessor);
                            }
                        }
                    }
                }
                finally
                {
                    // cleanup resources
                    _segmentsAvailableEvent.Dispose();
                }
            }, cancellationToken, TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning, TaskScheduler.Default);
        }

        #endregion // Methods

        #region EventHandlers

        protected void OnServiceSegmentsChanged(IApplicationService sender, ModelsChangedEventArgs e)
        {
            // if there were any modifications that might make new segments available, wake up
            if (e.Entries.Any(x => x.State == ModelState.Added || x.State == ModelState.Updated))
            {
                _segmentsAvailableEvent.Set();
            }
        }

        #endregion // EventHandlers
    }
}
